#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <sys/timeb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h> // file option
#include <dirent.h>
// #include <iostream>
// #include <sstream>
// #include <vector>
// #include <list>
// #include <map>
// #include <thread>
// #include <string>
// #include <iomanip>
// #include <fstream>
// #include <vector>
// #include <set>
// #include <algorithm>
// #include <unordered_map>
// #include <mutex>
// #include <atomic>
// #include <bits/wordsize.h>
// #include <bits/timesize.h>
using namespace std;
//需要添加串口头文件。
#include <termios.h> /* POSIX terminal control definitions */

//备注这里的flow_ctrl=0是cxx规则。如果不填，使用默认值0
static int openTtySerial(const char *filename, int speed, int databits, int stopbits, int parity, int flow_ctrl = 0)
{
    int i;
    int fd = -1;
    int statusflag = -1;
    int speed_arr[] = {B115200, B57600, B19200, B9600, B4800, B2400, B1200, B300};
    int name_arr[] = {115200, 57600, 19200, 9600, 4800, 2400, 1200, 300};

    struct termios options;
    fd = open(filename, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd < 0)
    {
        return -1;
    }
    /*  tcgetattr(fd,&options)得到与fd指向对象的相关参数，并将它们保存于options,该函数还可以测试配置是否正确，
    该串口是否可用等。若调用成功，函数返回值为0，若调用失败，函数返回值为1.  */
    if (tcgetattr(fd, &options) != 0)
    {
        printf("SetupSerial 1\n");
        goto closeFilePoint; //return(FALSE);
    }

    //设置串口输入波特率和输出波特率
    for (i = 0; i < (int)(sizeof(speed_arr) / sizeof(int)); i++)
    {
        if (speed == name_arr[i])
        {
            cfsetispeed(&options, speed_arr[i]);
            cfsetospeed(&options, speed_arr[i]);
            statusflag = -2;
            break;
        }
    }

    if(statusflag == -1)
    {
        printf("speed input err!\n");
        goto closeFilePoint;
    }

    //修改控制模式，保证程序不会占用串口
    options.c_cflag |= CLOCAL;
    //修改控制模式，使得能够从串口中读取输入数据
    options.c_cflag |= CREAD;

    //设置数据流控制
    switch (flow_ctrl)
    {
    case 0: //不使用流控制
        options.c_cflag &= ~CRTSCTS;
        break;

    case 1: //使用硬件流控制
        options.c_cflag |= CRTSCTS;
        break;
    case 2: //使用软件流控制
        options.c_cflag |= IXON | IXOFF | IXANY;
        break;
    }
    //设置数据位
    //屏蔽其他标志位
    options.c_cflag &= ~CSIZE;
    switch (databits)
    {
    case 5:
        options.c_cflag |= CS5;
        break;
    case 6:
        options.c_cflag |= CS6;
        break;
    case 7:
        options.c_cflag |= CS7;
        break;
    case 8:
        options.c_cflag |= CS8;
        break;
    default:
        fprintf(stderr, "Unsupported data size\n");
        goto closeFilePoint; //return(FALSE);
    }
    //设置校验位
    switch (parity)
    {
    case 'n':
    case 'N': //无奇偶校验位。
        options.c_cflag &= ~PARENB;
        options.c_iflag &= ~INPCK;
        break;
    case 'o':
    case 'O': //设置为奇校验
        options.c_cflag |= (PARODD | PARENB);
        options.c_iflag |= INPCK;
        break;
    case 'e':
    case 'E': //设置为偶校验
        options.c_cflag |= PARENB;
        options.c_cflag &= ~PARODD;
        options.c_iflag |= INPCK;
        break;
    case 's':
    case 'S': //设置为空格
        options.c_cflag &= ~PARENB;
        options.c_cflag &= ~CSTOPB;
        break;
    default:
        fprintf(stderr, "Unsupported parity\n");
        goto closeFilePoint; //return(FALSE);
    }
    // 设置停止位
    switch (stopbits)
    {
    case 1:
        options.c_cflag &= ~CSTOPB;
        break;
    case 2:
        options.c_cflag |= CSTOPB;
        break;
    default:
        fprintf(stderr, "Unsupported stop bits\n");
        goto closeFilePoint; //return(FALSE);
    }

    //修改输出模式，原始数据输出
    options.c_oflag &= ~OPOST;
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_lflag &= ~(ISIG | ICANON);
    //输入配置，原始数据输入。不配接收0x0b,0x0d，0x11,0x13有问题
    options.c_iflag &= ~(BRKINT | ICRNL | ISTRIP | IXON);
    
    //设置等待时间和最小接收字符
    // options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */
    // options.c_cc[VMIN] = 1; /* 读取字符的最少个数为1 */

    //如果发生数据溢出，接收数据，但是不再读取 刷新收到的数据但是不读
    tcflush(fd, TCIFLUSH);

    //激活配置 (将修改后的termios数据设置到串口中）
    if (tcsetattr(fd, TCSANOW, &options) != 0)
    {
        printf("com set error!\n");
        goto closeFilePoint; //return(FALSE);
    }
    statusflag = 0;
closeFilePoint:
    if (statusflag)
    {
        if (fd >= 0)
        {
            close(fd);
        }
        fd = -1;
    }
    return fd;
}

int main()
{
    //linux是通过文件描述符来操作文件，sock等。打开：open，读:read，写：write，记得不用了close。
    //我这里串口名是"/dev/ttyUSB0"，需要以实际情况输入串口名。
    //我做测试是把串口的RX和TX短接，所以先发送，然后读取看是不是一致。所以usleep(100 * 1000);延时了100ms，以便发送成功。
    int sfd = openTtySerial("/dev/ttyUSB0", 115200, 8, 1, 'n');
    printf("open id = %d\n", sfd);
    if(sfd < 0)
    {
        printf("open tty fail = %d\n", sfd);
        return -1;
    }

    const char *tmpsend = "1234567890abcdefg";
    char tmpread[128];
    //默认情况下串口文件读取会被阻塞，将文件描述符配置为非阻塞。
    //获取原来的flags
    int flags = fcntl(sfd, F_GETFL);//fd 为文件描述符
    // 设置新的flags
    flags |= O_NONBLOCK;
    // flags = flags | O_NONBLOCK;
    fcntl(sfd, F_SETFL, flags);
    
    //写
    flags = write(sfd, tmpsend, strlen(tmpsend));
    printf("%d\n", flags);

    //读
    //测试RX-TX短路，延时100ms，等待发送完成以及接受完成。
    usleep(100 * 1000);
    memset(tmpread, 0, sizeof(tmpread));
    flags = read(sfd, tmpread, sizeof(tmpread));
    printf("%d\n", flags);
    printf("read data = %s\n", tmpread);

    //关闭
    close(sfd);
}

// 输出：
// open id = 3
// 17
// 17
// read data = 1234567890abcdefg

